// (c) Copyright slimCODE Software Inc. - www.slimcode.com
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.Xml;

namespace SlimCode.SlimPasswords
{
  internal class PasswordScheme
  {
    public PasswordScheme( bool simple )
    {
      if( simple )
      {
        m_length = 12;
        m_symbols = false;
        m_webSafeSymbols = true;
      }
    }

    public bool Digits
    {
      get { return m_digits; }
      set { m_digits = value; }
    }

    public int Length
    {
      get { return m_length; }
      set
      {
        if( value <= 4 )
        {
          m_length = 4;
        }
        else if( value >= 1024 )
        {
          m_length = 1024;
        }
        else
        {
          m_length = value;
        }
      }
    }

    public bool LowercaseLetters
    {
      get { return m_lowercases; }
      set { m_lowercases = value; }
    }

    public bool Symbols
    {
      get { return m_symbols; }
      set
      {
        m_symbols = value;

        if( value )
        {
          m_webSafeSymbols = false;
        }
      }
    }

    public bool UppercaseLetters
    {
      get { return m_uppercases; }
      set { m_uppercases = value; }
    }

    public bool WebSafeSymbols
    {
      get { return m_webSafeSymbols; }
      set
      {
        m_webSafeSymbols = value;

        if( value )
        {
          m_symbols = false;
        }
      }
    }

    public string GeneratePassword()
    {
      StringBuilder charSet = new StringBuilder();

      if( this.LowercaseLetters )
      {
        charSet.Append( LowercaseChars );
      }

      if( this.UppercaseLetters )
      {
        charSet.Append( UppercaseChars );
      }

      if( this.Digits )
      {
        // Twice the chars, since it's small.
        charSet.Append( DigitChars );
        charSet.Append( DigitChars );
      }

      if( this.Symbols )
      {
        charSet.Append( SymbolChars );
      }

      if( this.WebSafeSymbols )
      {
        // Twice the chars, since it's small.
        charSet.Append( WebSafeSymbolChars );
        charSet.Append( WebSafeSymbolChars );
      }

      if( charSet.Length == 0 )
        return string.Empty;

      int index = 0;

      StringBuilder password = new StringBuilder( this.Length );
      RandomNumberGenerator random = RandomNumberGenerator.Create();
      byte[] randomData = new byte[ 8 ];

      while( password.Length < this.Length )
      {
        random.GetBytes( randomData );

        foreach( byte randomByte in randomData )
        {
          index += randomByte;
        }

        index %= charSet.Length;

        password.Append( charSet[ index ] );
      }

      return password.ToString();
    }

    public void Load( XmlNode parentNode )
    {
      XmlNode valueNode = parentNode.SelectSingleNode( ".//Length" );

      if( valueNode != null )
      {
        try
        {
          // This will limit values between 4 and 1024.
          this.Length = XmlConvert.ToInt32( valueNode.InnerText );
        }
        catch { }
      }

      valueNode = parentNode.SelectSingleNode( ".//LowercaseLetters" );

      if( valueNode != null )
      {
        this.LowercaseLetters = XmlConvert.ToBoolean( valueNode.InnerText );
      }

      valueNode = parentNode.SelectSingleNode( ".//UppercaseLetters" );

      if( valueNode != null )
      {
        this.UppercaseLetters = XmlConvert.ToBoolean( valueNode.InnerText );
      }

      valueNode = parentNode.SelectSingleNode( ".//Digits" );

      if( valueNode != null )
      {
        this.Digits = XmlConvert.ToBoolean( valueNode.InnerText );
      }

      valueNode = parentNode.SelectSingleNode( ".//Symbols" );

      if( valueNode != null )
      {
        this.Symbols = XmlConvert.ToBoolean( valueNode.InnerText );
      }

      valueNode = parentNode.SelectSingleNode( ".//WebSafeSymbols" );

      if( valueNode != null )
      {
        this.WebSafeSymbols = XmlConvert.ToBoolean( valueNode.InnerText );
      }
    }

    public void Save( XmlWriter writer )
    {
      writer.WriteElementString( "Length", XmlConvert.ToString( this.Length ) );
      writer.WriteElementString( "LowercaseLetters", XmlConvert.ToString( this.LowercaseLetters ) );
      writer.WriteElementString( "UppercaseLetters", XmlConvert.ToString( this.UppercaseLetters ) );
      writer.WriteElementString( "Digits", XmlConvert.ToString( this.Digits ) );
      writer.WriteElementString( "Symbols", XmlConvert.ToString( this.Symbols ) );
      writer.WriteElementString( "WebSafeSymbols", XmlConvert.ToString( this.WebSafeSymbols ) );
    }

    private int m_length = 15;
    private bool m_lowercases = true;
    private bool m_uppercases = true;
    private bool m_digits = true;
    private bool m_symbols = true;
    private bool m_webSafeSymbols; // = false

    private const string LowercaseChars = "abcdefghijklmnopqrstuvwxyz";
    private const string UppercaseChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    private const string DigitChars = "0123456789";
    private const string SymbolChars = @"!""#$%&'()*+,-./:;<=>?@[\]^_`{|}~";
    private const string WebSafeSymbolChars = @"-_.!~*'()";  // RFC2396, section 2.3
  }
}
